package com.bluexmicro.bluetooth.peripheral

import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import com.bluexmicro.bluetooth.LogUtils
import com.bluexmicro.bluetooth.connectBleDevice
import com.bluexmicro.bluetooth.hasProperty
import com.bluexmicro.bluetooth.model.ConnectionPriority
import com.bluexmicro.bluetooth.model.WriteType
import com.bluexmicro.bluetooth.peripheral.handler.BatchWriteTasksHandler
import com.bluexmicro.bluetooth.peripheral.handler.DiscoverServicesHandler
import com.bluexmicro.bluetooth.peripheral.handler.PhyHandler
import com.bluexmicro.bluetooth.peripheral.handler.ReadDescriptorHandler
import com.bluexmicro.bluetooth.peripheral.handler.ReadHandler
import com.bluexmicro.bluetooth.peripheral.handler.RequestMtuHandler
import com.bluexmicro.bluetooth.peripheral.handler.WriteDescriptorHandler
import com.bluexmicro.bluetooth.peripheral.handler.WriteHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume

enum class ConnectionState {
    IDLE, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED
}

private const val CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"
private val UUID_CLIENT_CHARACTERISTIC_CONFIG =
    UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG)
typealias OnConnectionStateCallback = (gatt: BluetoothGatt, status: Int, newState: Int) -> Unit

data class BluetoothValue(
    val mac: String,
    val service: String,
    val characteristic: String,
    val value: ByteArray
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as BluetoothValue

        if (mac != other.mac) return false
        if (service != other.service) return false
        if (characteristic != other.characteristic) return false
        if (!value.contentEquals(other.value)) return false

        return true
    }

    override fun hashCode(): Int {
        var result = mac.hashCode()
        result = 31 * result + service.hashCode()
        result = 31 * result + characteristic.hashCode()
        result = 31 * result + value.contentHashCode()
        return result
    }
}

data class PeripheralState(
    val mac: String,
    val state: Boolean
)

class PeripheralManager {

    private var scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
    private val peripheralCache = ConcurrentHashMap<String, BluetoothPeripheral>()
    private var onConnectionStateCallback: OnConnectionStateCallback? = null
    private val mValueShared = MutableSharedFlow<BluetoothValue>()
    private val mPeripheralStateShared = MutableSharedFlow<PeripheralState>()

    private fun resetGattCallback(): BluetoothGattCallback {
        return object : BluetoothGattCallback() {

            private fun BluetoothGatt.findPeripheral(): BluetoothPeripheral? {
                return peripheralCache[device.address]
            }

            override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
                Log.d(
                    "TAG",
                    "onConnectionStateChange() called with: gatt = $gatt, status = $status, newState = $newState"
                )
//            super.onConnectionStateChange(gatt, status, newState)
                peripheralCache[gatt.device.address]?.apply {
                    innerState.value = when (newState) {
                        BluetoothProfile.STATE_CONNECTED -> ConnectionState.CONNECTED
                        BluetoothProfile.STATE_CONNECTING -> ConnectionState.CONNECTING
                        BluetoothProfile.STATE_DISCONNECTED -> ConnectionState.DISCONNECTED
                        BluetoothProfile.STATE_DISCONNECTING -> ConnectionState.DISCONNECTING
                        else -> ConnectionState.IDLE
                    }
                }
                scope.launch {
                    when (newState) {
                        BluetoothProfile.STATE_CONNECTED ->
                            mPeripheralStateShared.emit(PeripheralState(gatt.device.address, true))

                        BluetoothProfile.STATE_DISCONNECTED ->
                            mPeripheralStateShared.emit(PeripheralState(gatt.device.address, false))
                    }
                }

                onConnectionStateCallback?.invoke(gatt, status, newState)
            }

            override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
                gatt.findPeripheral()?.takeIf { it.discoverServicesHandler.isActive() }?.apply {
                    discoverServicesHandler.onServicesDiscovered(gatt, status)
                }
            }

            override fun onCharacteristicRead(
                gatt: BluetoothGatt,
                characteristic: BluetoothGattCharacteristic,
                status: Int
            ) {
                //support old version
                gatt.findPeripheral()?.takeIf { it.readHandler.isActive() }?.apply {
                    readHandler.onCharacteristicRead(
                        gatt,
                        characteristic,
                        characteristic.value,
                        status
                    )
                }
            }

            override fun onCharacteristicRead(
                gatt: BluetoothGatt,
                characteristic: BluetoothGattCharacteristic,
                value: ByteArray,
                status: Int
            ) {
                gatt.findPeripheral()?.takeIf { it.readHandler.isActive() }?.apply {
                    readHandler.onCharacteristicRead(gatt, characteristic, value, status)
                }
            }

            override fun onCharacteristicWrite(
                gatt: BluetoothGatt,
                characteristic: BluetoothGattCharacteristic,
                status: Int
            ) {
                gatt.findPeripheral()
                    ?.takeIf { it.writeHandler.isActive() || it.batchWriteTasksHandler.isActive() }
                    ?.apply {
                        if (writeHandler.isActive()) {
                            writeHandler.onCharacteristicWrite(gatt, characteristic, status)
                        } else if (batchWriteTasksHandler.isActive()) {
                            batchWriteTasksHandler.onCharacteristicWrite(
                                gatt,
                                characteristic,
                                status
                            )
                        }
                    }
            }

            override fun onCharacteristicChanged(
                gatt: BluetoothGatt,
                characteristic: BluetoothGattCharacteristic
            ) {

                scope.launch {
                    mValueShared.emit(
                        BluetoothValue(
                            gatt.device.address,
                            characteristic.service.uuid.toString(),
                            characteristic.uuid.toString(),
                            characteristic.value
                        )
                    )
                }

            }

            override fun onCharacteristicChanged(
                gatt: BluetoothGatt,
                characteristic: BluetoothGattCharacteristic,
                value: ByteArray
            ) {
                scope.launch {
                    mValueShared.emit(
                        BluetoothValue(
                            gatt.device.address,
                            characteristic.service.uuid.toString(),
                            characteristic.uuid.toString(),
                            value
                        )
                    )
                }

            }

            override fun onDescriptorRead(
                gatt: BluetoothGatt,
                descriptor: BluetoothGattDescriptor,
                status: Int
            ) {
                gatt.findPeripheral()?.takeIf { it.readDescriptorHandler.isActive() }?.apply {
                    readDescriptorHandler.onDescriptorRead(
                        gatt,
                        descriptor,
                        descriptor.value,
                        status
                    )
                }
            }

            override fun onDescriptorRead(
                gatt: BluetoothGatt,
                descriptor: BluetoothGattDescriptor,
                status: Int,
                value: ByteArray
            ) {
                gatt.findPeripheral()?.takeIf { it.readDescriptorHandler.isActive() }?.apply {
                    readDescriptorHandler.onDescriptorRead(gatt, descriptor, value, status)
                }
            }

            override fun onDescriptorWrite(
                gatt: BluetoothGatt,
                descriptor: BluetoothGattDescriptor,
                status: Int
            ) {
                gatt.findPeripheral()?.takeIf { it.writeDescriptorHandler.isActive() }?.apply {
                    writeDescriptorHandler.onDescriptorWrite(gatt, descriptor, status)
                }
            }

            override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
                Log.d(
                    "TAG",
                    "onMtuChanged() called with: gatt = $gatt, mtu = $mtu, status = $status"
                )
                gatt.findPeripheral()?.takeIf { it.requestMtuHandler.isActive() }?.apply {
                    requestMtuHandler.onMtuChanged(gatt, mtu, status)
                }
            }

            override fun onServiceChanged(gatt: BluetoothGatt) {
                super.onServiceChanged(gatt)
            }

            override fun onPhyRead(gatt: BluetoothGatt, txPhy: Int, rxPhy: Int, status: Int) {
                gatt.findPeripheral()?.takeIf { it.phyHandler.isReadActive() }?.apply {
                    phyHandler.onPhyRead(gatt, txPhy, rxPhy, status)
                }
            }

            override fun onPhyUpdate(gatt: BluetoothGatt, txPhy: Int, rxPhy: Int, status: Int) {
                gatt.findPeripheral()?.takeIf { it.phyHandler.isSetActive() }?.apply {
                    phyHandler.onPhyUpdate(gatt, txPhy, rxPhy, status)
                }
            }

        }
    }

    private var innerGattCallback = resetGattCallback()

    fun Intent.getBluetoothDevice(): BluetoothDevice? {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            getParcelableExtra(
                BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java
            )
        } else {
            getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
        }
    }


    private val adapterStateReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {

            when (intent.action) {
                BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED -> {
                    val intentDevice: BluetoothDevice = intent.getBluetoothDevice() ?: return
                    val state = intent.getIntExtra(
                        BluetoothAdapter.EXTRA_CONNECTION_STATE,
                        BluetoothProfile.STATE_DISCONNECTED
                    )
                    peripheralCache[intentDevice.address]?.apply {
                        when (state) {
                            BluetoothProfile.STATE_DISCONNECTED -> innerState.update { ConnectionState.DISCONNECTED }
                            BluetoothProfile.STATE_CONNECTED -> innerState.update { ConnectionState.CONNECTED }
                        }
                    }
                }

                BluetoothDevice.ACTION_ACL_CONNECTED -> {
                    val intentDevice: BluetoothDevice = intent.getBluetoothDevice() ?: return
                    peripheralCache[intentDevice.address]?.apply {
                        innerState.update { ConnectionState.CONNECTED }
                    }
                }

                BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
                    val intentDevice: BluetoothDevice = intent.getBluetoothDevice() ?: return
                    peripheralCache[intentDevice.address]?.apply {
                        innerState.update { ConnectionState.DISCONNECTED }
                    }
                }

                BluetoothAdapter.ACTION_STATE_CHANGED -> {
                    //蓝牙关闭时，蓝牙设备会断开连接，但是不用通知蓝牙连接状态更新，所以这里在蓝牙打开后重连设备
//                    val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
//                    if (state == BluetoothAdapter.STATE_OFF) {} else if (state == BluetoothAdapter.STATE_ON) {}
                    peripheralCache.forEach {
                        it.value.innerState.update {
                            ConnectionState.DISCONNECTED
                        }
                    }
                }
            }
        }
    }

    fun retrieveConnectedPeripherals(): List<BluetoothPeripheral> {
        return peripheralCache.filter {
            it.value.innerState.value == ConnectionState.CONNECTED
        }.map {
            it.value
        }
    }

    fun retrievePeripherals(context: Context, mac: String): BluetoothPeripheral? {
        var peripheral = peripheralCache[mac]
        Log.d("retrievePeripherals: ", "peripheral: $peripheral")
        if (peripheral == null) {
            val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?
            val bluetoothAdapter = manager?.adapter
            val device = bluetoothAdapter?.getRemoteDevice(mac)
            Log.d("retrievePeripherals: ", "device: $device")
            if (device != null) {
                peripheral = BluetoothPeripheral(device)
                peripheralCache[mac] = peripheral
                return peripheral
            }
        }
        return peripheral
    }

    fun observeCharacteristicValue(
        mac: String,
        service: String,
        characteristic: String
    ): Flow<BluetoothValue> {
        return mValueShared.filter {
            it.mac.equals(mac, true) && UUID.fromString(it.service)
                .equals(UUID.fromString(service)) && UUID.fromString(it.characteristic)
                .equals(UUID.fromString(characteristic))
        }
    }

    fun observePeripheralState(mac: String): Flow<PeripheralState> {
        return mPeripheralStateShared.filter {
            it.mac.equals(mac, true)
        }
    }


    suspend fun write(
        mac: String,
        service: String,
        characteristic: String,
        type: WriteType,
        value: ByteArray
    ) {
        val peripheral = requireNotNull(peripheralCache[mac]) { "无效设备，请先连接设备" }
        val targetService = requireNotNull(
            peripheral.services.find { it.uuid.equals(UUID.fromString(service)) }
        ) { "找不到services" }
        val targetCharacteristic = requireNotNull(
            targetService.getCharacteristic(UUID.fromString(characteristic))
        ) { "找不到 targetCharacteristic" }

        peripheral.write(targetCharacteristic, type, value)
    }

    private fun setConnectionStateCallback(cb: OnConnectionStateCallback) {
        onConnectionStateCallback = cb
    }

    /**
     * todo 如果参数不一样重新连接
     * 已经连接的也会再连接一次
     * 这里不一定连接成功
     */
    suspend fun connect(
        context: Context,
        peripheral: BluetoothPeripheral,
        autoConnect: Boolean
    ): Result<Any> = suspendCancellableCoroutine {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if (ActivityCompat.checkSelfPermission(
                    context,
                    Manifest.permission.BLUETOOTH_CONNECT
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                it.resume(Result.failure(IllegalStateException("todo 权限")))
            }
        }
        innerGattCallback = resetGattCallback()
        setConnectionStateCallback { gatt, lastState, newState ->
            when (newState) {
                BluetoothProfile.STATE_CONNECTED -> {
                    onConnectionStateCallback = null
                    peripheral.gatt = gatt
                    peripheral.innerState.value = ConnectionState.CONNECTED
                    it.resume(Result.success(Unit))
                }

                BluetoothProfile.STATE_DISCONNECTED -> {
                    Log.e("TAG", "connect: $lastState")
                    if (lastState == 0x101 || lastState == 133) {
                        peripheral.close()
                    }
                    onConnectionStateCallback = null
                    it.resume(Result.failure(IllegalStateException("连接失败")))
                }
            }
        }

        val gatt = context.connectBleDevice(peripheral.device, innerGattCallback, autoConnect)
        if (gatt == null) {
            it.resume(Result.failure(IllegalStateException("连接失败")))
        }
    }

    fun releaseBluetoothPeripheral(peripheral: BluetoothPeripheral, disconnect: Boolean = true) {
        if (disconnect) {
            peripheral.disconnect()
        }
        peripheralCache.remove(peripheral.device.address)
    }

    private val initialized = AtomicBoolean(false)

    fun launch(context: Context) {
        if (initialized.compareAndSet(false, true)) {
            scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
            LogUtils.setup(context)
            val intentFilter = IntentFilter()
            intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
            intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
            intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
            intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
//        ContextCompat.registerReceiver(context, adapterStateReceiver, intentFilter, ContextCompat.RECEIVER_EXPORTED)
            context.registerReceiver(adapterStateReceiver, intentFilter)
        }
    }

    fun release(context: Context) {
        initialized.compareAndSet(true, false)
        context.unregisterReceiver(adapterStateReceiver)
        peripheralCache.clear()
        scope.cancel()
    }


    /**
     * 外部无法创建
     */
    inner class BluetoothPeripheral(val device: BluetoothDevice) {

        var gatt: BluetoothGatt? = null
        var mtuSize = 23
        var services = emptyList<BluetoothGattService>()
        internal val innerState = MutableStateFlow(ConnectionState.IDLE)
        val state: StateFlow<ConnectionState> = innerState

        private fun requestGatt(): BluetoothGatt = requireNotNull(gatt) {
            "Please Connect first"
        }

        val writeHandler = WriteHandler()
        val batchWriteTasksHandler = BatchWriteTasksHandler()
        val readHandler = ReadHandler()
        val readDescriptorHandler = ReadDescriptorHandler()
        val writeDescriptorHandler = WriteDescriptorHandler()
        val requestMtuHandler = RequestMtuHandler()
        val phyHandler = PhyHandler()

        //        val readRssiHandler = ReadRssiHandler()
        val discoverServicesHandler = DiscoverServicesHandler()

        suspend fun discoverServices() =
            discoverServicesHandler.execute(requestGatt(), false).apply {
                services = getOrNull() ?: emptyList()
            }

        suspend fun write(
            characteristic: BluetoothGattCharacteristic,
            type: WriteType,
            bytes: ByteArray
        ) = writeHandler.execute(requestGatt(), characteristic, type, bytes)

        suspend fun writeMultiPacket(
            characteristic: BluetoothGattCharacteristic,
            packages: List<ByteArray>
        ) = batchWriteTasksHandler.execute(requestGatt(), characteristic, packages)

        suspend fun read(characteristic: BluetoothGattCharacteristic) =
            readHandler.execute(requestGatt(), characteristic)

        suspend fun readDescriptor(descriptor: BluetoothGattDescriptor) =
            readDescriptorHandler.execute(requestGatt(), descriptor)

        suspend fun writeDescriptor(descriptor: BluetoothGattDescriptor, bytes: ByteArray) =
            writeDescriptorHandler.execute(requestGatt(), descriptor, bytes)

        suspend fun requestMTU(size: Int) =
            requestMtuHandler.execute(requestGatt(), size).apply {
                mtuSize = getOrNull() ?: 23
            }

        @SuppressLint("MissingPermission")
        fun requestMTUDict(size: Int): Boolean {
            return requestGatt().requestMtu(size)
        }

        suspend fun requestPhy(tx: Int, rx: Int) =
            phyHandler.executeSet(requestGatt(), tx, rx, 0)


        suspend fun readPhy() = phyHandler.executeRead(requestGatt())

        @SuppressLint("MissingPermission")
        fun requestConnectionPriority(priority: ConnectionPriority) =
            requestGatt().requestConnectionPriority(priority.value)

        @SuppressLint("MissingPermission")
        suspend fun setNotification(
            characteristic: BluetoothGattCharacteristic,
            enable: Boolean
        ) {
            val res = requestGatt().setCharacteristicNotification(characteristic, true)
            if (res) {
                val descriptor = characteristic.getDescriptor(UUID_CLIENT_CHARACTERISTIC_CONFIG)
                val notify =
                    characteristic.hasProperty(BluetoothGattCharacteristic.PROPERTY_NOTIFY)
                val value = if (enable) {
                    if (notify) BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE else BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
                } else {
                    BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
                }
                delay(100)
                writeDescriptorHandler.execute(requestGatt(), descriptor, value)
            } else {
                Result.failure(IllegalStateException(""))
            }
        }


        @SuppressLint("MissingPermission")
        fun disconnect() {
            gatt?.disconnect()

        }

        @SuppressLint("MissingPermission")
        fun close() {
            gatt?.close()
        }
    }

}